﻿@*参考 https://learn.microsoft.com/zh-cn/windows/uwp/devices-sensors/gatt-client*@

@using Windows.Devices.Bluetooth
@using Windows.Devices.Bluetooth.GenericAttributeProfile
@using Windows.Devices.Enumeration
@using Windows.Devices.Bluetooth.Advertisement
@using System.Diagnostics
@using zy_simu.Data
@using Windows.Storage.Streams
@using System.Runtime.InteropServices.WindowsRuntime;

<style>
    .cell_mark {
        --bs-table-bg: lightyellow;
    }
</style>

<h5>BleConfigDialog</h5>

<div>
    <div class="row">
        <Table @ref="@DevTable" TItem="BleDeviceDes" Items="@DevData.Take(DevData.Count)" @bind-SelectedRows="SelectedItems"
           IsStriped="true" IsBordered="true" Height="500" AllowResizing="true" IsFixedHeader="true"
           style="--bb-table-td-padding-x: .1rem; --bb-table-td-padding-y: .1rem;"
           ClickToSelect="true" DoubleClickToEdit="true" OnSaveAsync="@OnSaveAsync">
        <TableColumns>
            <TableColumn @bind-Field="@context.name" Text="名称" Width="120" Align="Alignment.Center" Readonly="true" />
            <TableColumn @bind-Field="@context.mac" Text="MAC地址" Width="120" Align="Alignment.Center" />
            <TableColumn @bind-Field="@context.manufacturer_data" Text="产商数据" Align="Alignment.Center" Readonly="true" />
        </TableColumns>
        </Table>
    </div>

    <Divider />

    <div class="row">
        <div class="col-auto">
            <button class="btn btn-primary" @onclick="btn_ble_connect"> 连接设备 </button>
        </div>
        <div class="col-auto">
            <button class="btn btn-primary" @onclick="btn_ble_write"> 发送测试字符串 </button>
        </div>
        <div class="col-auto">
            @TestString
        </div>

    </div>
</div>


@code {
    public class BleDeviceDes
    {
        public string name { get; set; } = "";
        public string mac { get; set; } = "";
        public string manufacturer_data { get; set; } = "";
    }

    public Table<BleDeviceDes>? DevTable { get; set; }
    public List<BleDeviceDes>? DevData { get; set; }
    public List<BleDeviceDes>? SelectedItems { get; set; }

    private readonly BluetoothLEAdvertisementWatcher advertisementWatcher = new BluetoothLEAdvertisementWatcher();

    protected override void OnInitialized()
    {
        DevData = new List<BleDeviceDes>();
        SelectedItems = new List<BleDeviceDes>();
        for(int i = 0; i < 5; i++)
        {
            BleDeviceDes item = new();
            item.name = $"{i}.name";
            item.mac = $"{i}.mac_xx_xx_xx";
            item.manufacturer_data = $"{i}.manufacturer_data";
            DevData.Add(item);
        }

        // string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };

        // DeviceWatcher deviceWatcher = DeviceInformation.CreateWatcher(
        //                     BluetoothLEDevice.GetDeviceSelectorFromConnectionStatus(BluetoothConnectionStatus.Disconnected),
        //                     requestedProperties,
        //                     DeviceInformationKind.AssociationEndpoint);
        advertisementWatcher.ScanningMode = BluetoothLEScanningMode.Active;
        advertisementWatcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;
        advertisementWatcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;
        advertisementWatcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMicroseconds(5000);
        advertisementWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMicroseconds(2000);
        advertisementWatcher.Received += AdvertisementWatcher_Received;
        advertisementWatcher.Stopped += AdvertisementWatcher_Stopped;
        advertisementWatcher.Start();

        KgConsoleManager.Show();

        base.OnInitialized();
    }

    private void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
        // dispose repeated data
        for(int i = 0; i < DevData.Count(); i++)
        {
            if (DevData[i].mac == args.BluetoothAddress.ToString("X")) return;
        }
        InvokeAsync(() =>
        {
            BleDeviceDes item = new();
            item.name = args.Advertisement.LocalName;
            item.mac = args.BluetoothAddress.ToString("X");

            // Check if there are any manufacturer-specific sections.
            // If there is, print the raw data of the first manufacturer section (if there are multiple).
            string manufacturerDataString = "";
            var manufacturerSections = args.Advertisement.ManufacturerData;
            var res = String.Empty;
            if (manufacturerSections.Count > 0)
            {
                // Only print the first one of the list
                var manufacturerData = manufacturerSections[0];
                var data = new byte[manufacturerData.Data.Length];
                string str;
                using (var reader = Windows.Storage.Streams.DataReader.FromBuffer(manufacturerData.Data))
                {
                    // reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                    // item.manufacturer_data = reader.ReadString(manufacturerData.Data.Length);
                    reader.ReadBytes(data);
                }

                item.manufacturer_data = BitConverter.ToString(data);

                // string[] hexValuesSplit = str.Split('-');
                // foreach (String hex in hexValuesSplit)
                // {
                //     int value = Convert.ToInt32(hex, 16);
                //     string stringValue = Char.ConvertFromUtf32(value);
                //     char charValue = (char)value;
                //     res += charValue;
                // }

                // manufacturerDataString = string.Format("0x{0}: {1}", manufacturerData.CompanyId.ToString("X"), res);

            }

            DevData.Add(item);
            StateHasChanged();
        });
    }
    private void AdvertisementWatcher_Stopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
    {
        //     Log.Info("AdvertisementWatcher_Stopped");
        //     Stopped?.Invoke(this, args);
    }

    private Task<bool> OnSaveAsync(BleDeviceDes item, ItemChangedType changedType)
    {
        if (changedType == ItemChangedType.Update)
        {
            int cnt = DevData.Count();
            // modified_idx = -1;
            // for (int i = 0; i < cnt; i++)
            // {
            //     if (DevData[i].name == item.name)
            //     {
            //         if (item.value < DevData[i].min || item.value > DevData[i].max)
            //             break;
            //         // DevData[i].value = item.value; 不主动更新，等待父组件统一更新
            //         modified_value = item.value;
            //         modified_idx = i;
            //         if ((PM_ENUM)i == PM_ENUM.PM_TS)
            //         {
            //             DevData[(int)PM_ENUM.PM_R1].max = item.value;
            //             DevData[(int)PM_ENUM.PM_R2].min = item.value;
            //         }
            //         else if ((PM_ENUM)i == PM_ENUM.PM_R1)
            //         {
            //             DevData[(int)PM_ENUM.PM_TS].min = item.value;
            //         }
            //         else if ((PM_ENUM)i == PM_ENUM.PM_R2)
            //         {
            //             DevData[(int)PM_ENUM.PM_TS].max = item.value;
            //         }
            //         break;
            //     }
            // }
        }
        return Task.FromResult(true);
    }

    private string TestString = "";
    BluetoothLEDevice BleDev;
    readonly int E_DEVICE_NOT_AVAILABLE = unchecked((int)0x800710df); // HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE)

    private List<GattDeviceService> GattServices = new();
    GattDeviceService? SelectedService = null;
    IReadOnlyList<GattCharacteristic>? characteristics = null;
    GattCharacteristic? GattCharRead = null;
    GattCharacteristic? GattCharWrite = null;

    private async Task btn_ble_write()
    {
        if(GattCharWrite != null)
        {
            string str = "12345678901234567890xxx";
            byte[] buf = System.Text.Encoding.UTF8.GetBytes(str);
            int cnt = buf.Length;
            // writer.WriteString("1234567890xxx1234567890xxx123456789xxx01234567890xxx");
            try
            {
                for (int i = 0, len = 0; i < cnt; i += len)
                {
                    len = cnt - i;
                    if (len > 20) len = 20;

                    if (GattCharWrite != null)
                    {
                        //var result = GattCharWrite.WriteValueWithResultAsync(buf.AsBuffer(0, cnt)).GetResults();
                        var result = await GattCharWrite.WriteValueWithResultAsync(buf.AsBuffer(i, len));
                        if (result.Status == GattCommunicationStatus.Success)
                        {
                            // Successfully wrote to device
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex.Message);
                GattCharWrite.Service.Dispose();
            }
        }
    }

    private async Task btn_ble_connect()
    {
        UInt64 mac = 0;
        if (SelectedItems.Count() <= 0)
            return;
        else
            mac = Convert.ToUInt64(SelectedItems[0].mac, 16);
        try
        {
            // BT_Code: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
            BleDev = await BluetoothLEDevice.FromBluetoothAddressAsync(mac);
            Debug.Assert(BleDev != null);
        }
        catch (Exception ex) when (ex.HResult == E_DEVICE_NOT_AVAILABLE)
        {
            // rootPage.NotifyUser("Bluetooth radio is not on.", NotifyType.ErrorMessage);
        }

        if (BleDev != null)
        {
            // Note: BluetoothLEDevice.GattServices property will return an empty list for unpaired devices. For all uses we recommend using the GetGattServicesAsync method.
            // BT_Code: GetGattServicesAsync returns a list of all the supported services of the device (even if it's not paired to the system).
            // If the services supported by the device are expected to change during BT usage, subscribe to the GattServicesChanged event.
            GattDeviceServicesResult result = await BleDev.GetGattServicesAsync(BluetoothCacheMode.Uncached);

            if (result.Status == GattCommunicationStatus.Success)
            {
                var services = result.Services;
                System.Console.WriteLine(String.Format("Found {0} services", services.Count));
                foreach (var service in services)
                {
                    System.Console.WriteLine(GetServiceName(service));
                    GattServices.Add(service);
                    if (GetServiceName(service) == "65520") SelectedService = service;
                    // ServiceList.Items.Add(new ComboBoxItem { Content = DisplayHelpers.GetServiceName(service), Tag = service });
                }
                // ConnectButton.Visibility = Visibility.Collapsed;
                // ServiceList.Visibility = Visibility.Visible;
            }
            else
            {
                // rootPage.NotifyUser("Device unreachable", NotifyType.ErrorMessage);
            }
        }
        await Task.Delay(1000);
        if(SelectedService != null)
        {
            try
            {
                // Ensure we have access to the device.
                var accessStatus = await SelectedService.RequestAccessAsync();
                if (accessStatus == DeviceAccessStatus.Allowed)
                {
                    // BT_Code: Get all the child characteristics of a service. Use the cache mode to specify uncached characterstics only
                    // and the new Async functions to get the characteristics of unpaired devices as well.
                    var result = await SelectedService.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);
                    if (result.Status == GattCommunicationStatus.Success)
                    {
                        characteristics = result.Characteristics;
                        foreach (var characteristic in characteristics)
                        {
                            System.Console.WriteLine(GetCharacteristicName(characteristic));
                            if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write) 
                            || characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse))
                            {
                                GattCharWrite = characteristic;
                            }
                            // else if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate)
                            // || characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                            else if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                            {
                                GattCharRead = characteristic;
                                var cccdValue = GattClientCharacteristicConfigurationDescriptorValue.Notify;
                                try
                                {
                                    // BT_Code: Must write the CCCD in order for server to send indications.
                                    // We receive them in the ValueChanged event handler.
                                    GattCommunicationStatus status = await GattCharRead.WriteClientCharacteristicConfigurationDescriptorAsync(cccdValue);
                                    Debug.Assert(status == GattCommunicationStatus.Success);
                                    GattCharRead.ValueChanged += Characteristic_ValueChanged;
                                }
                                catch (UnauthorizedAccessException ex)
                                {
                                    // This usually happens when a device reports that it support indicate, but it actually doesn't.
                                }
                            }
                        }
                    }
                    else
                    {
                        // On error, act as if there are no characteristics.
                        characteristics = new List<GattCharacteristic>();
                    }
                }
                else
                {
                    // On error, act as if there are no characteristics.
                    characteristics = new List<GattCharacteristic>();

                }
            }
            catch (Exception ex)
            {
                // On error, act as if there are no characteristics.
                characteristics = new List<GattCharacteristic>();
            }

        }

    }

    private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
    {
        // BT_Code: An Indicate or Notify reported that the value has changed.
        // Display the new value with a timestamp.
        byte[] data = Utilities.ReadBufferToBytes(args.CharacteristicValue);
        string str = System.Text.Encoding.UTF8.GetString(data);
        System.Console.WriteLine(str);
    }

    public string GetServiceName(GattDeviceService service)
    {
        if (IsSigDefinedUuid(service.Uuid))
        {
            GattNativeServiceUuid serviceName;
            if (Enum.TryParse(Utilities.ConvertUuidToShortId(service.Uuid).ToString(), out serviceName))
            {
                return serviceName.ToString();
            }
        }
        return "Custom Service: " + service.Uuid;
    }
    public string GetCharacteristicName(GattCharacteristic characteristic)
    {
        if (IsSigDefinedUuid(characteristic.Uuid))
        {
            GattNativeCharacteristicUuid characteristicName;
            if (Enum.TryParse(Utilities.ConvertUuidToShortId(characteristic.Uuid).ToString(), out characteristicName))
            {
                return characteristicName.ToString();
            }
        }

        if (!string.IsNullOrEmpty(characteristic.UserDescription))
        {
            return characteristic.UserDescription;
        }

        else
        {
            return "Custom Characteristic: " + characteristic.Uuid;
        }
    }

    private bool IsSigDefinedUuid(Guid uuid)
    {
        var bluetoothBaseUuid = new Guid("00000000-0000-1000-8000-00805F9B34FB");

        var bytes = uuid.ToByteArray();
        // Zero out the first and second bytes
        // Note how each byte gets flipped in a section - 1234 becomes 34 12
        // Example Guid: 35918bc9-1234-40ea-9779-889d79b753f0
        //                   ^^^^
        // bytes output = C9 8B 91 35 34 12 EA 40 97 79 88 9D 79 B7 53 F0
        //                ^^ ^^
        bytes[0] = 0;
        bytes[1] = 0;
        var baseUuid = new Guid(bytes);
        return baseUuid == bluetoothBaseUuid;
    }

    public static class Utilities
    {
        /// <summary>
        ///     Converts from standard 128bit UUID to the assigned 32bit UUIDs. Makes it easy to compare services
        ///     that devices expose to the standard list.
        /// </summary>
        /// <param name="uuid">UUID to convert to 32 bit</param>
        /// <returns></returns>
        public static ushort ConvertUuidToShortId(Guid uuid)
        {
            // Get the short Uuid
            var bytes = uuid.ToByteArray();
            var shortUuid = (ushort)(bytes[0] | (bytes[1] << 8));
            return shortUuid;
        }

        /// <summary>
        ///     Converts from a buffer to a properly sized byte array
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static byte[] ReadBufferToBytes(IBuffer buffer)
        {
            var dataLength = buffer.Length;
            var data = new byte[dataLength];
            using (var reader = DataReader.FromBuffer(buffer))
            {
                reader.ReadBytes(data);
            }
            return data;
        }
    }




    /// <summary>
    ///     This enum assists in finding a string representation of a BT SIG assigned value for Service UUIDS
    ///     Reference: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
    /// </summary>
    public enum GattNativeServiceUuid : ushort
    {
        None = 0,
        AlertNotification = 0x1811,
        Battery = 0x180F,
        BloodPressure = 0x1810,
        CurrentTimeService = 0x1805,
        CyclingSpeedandCadence = 0x1816,
        DeviceInformation = 0x180A,
        GenericAccess = 0x1800,
        GenericAttribute = 0x1801,
        Glucose = 0x1808,
        HealthThermometer = 0x1809,
        HeartRate = 0x180D,
        HumanInterfaceDevice = 0x1812,
        ImmediateAlert = 0x1802,
        LinkLoss = 0x1803,
        NextDSTChange = 0x1807,
        PhoneAlertStatus = 0x180E,
        ReferenceTimeUpdateService = 0x1806,
        RunningSpeedandCadence = 0x1814,
        ScanParameters = 0x1813,
        TxPower = 0x1804,
        SimpleKeyService = 0xFFE0
    }

    /// <summary>
    ///     This enum is nice for finding a string representation of a BT SIG assigned value for Characteristic UUIDs
    ///     Reference: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
    /// </summary>
    public enum GattNativeCharacteristicUuid : ushort
    {
        None = 0,
        AlertCategoryID = 0x2A43,
        AlertCategoryIDBitMask = 0x2A42,
        AlertLevel = 0x2A06,
        AlertNotificationControlPoint = 0x2A44,
        AlertStatus = 0x2A3F,
        Appearance = 0x2A01,
        BatteryLevel = 0x2A19,
        BloodPressureFeature = 0x2A49,
        BloodPressureMeasurement = 0x2A35,
        BodySensorLocation = 0x2A38,
        BootKeyboardInputReport = 0x2A22,
        BootKeyboardOutputReport = 0x2A32,
        BootMouseInputReport = 0x2A33,
        CSCFeature = 0x2A5C,
        CSCMeasurement = 0x2A5B,
        CurrentTime = 0x2A2B,
        DateTime = 0x2A08,
        DayDateTime = 0x2A0A,
        DayofWeek = 0x2A09,
        DeviceName = 0x2A00,
        DSTOffset = 0x2A0D,
        ExactTime256 = 0x2A0C,
        FirmwareRevisionString = 0x2A26,
        GlucoseFeature = 0x2A51,
        GlucoseMeasurement = 0x2A18,
        GlucoseMeasurementContext = 0x2A34,
        HardwareRevisionString = 0x2A27,
        HeartRateControlPoint = 0x2A39,
        HeartRateMeasurement = 0x2A37,
        HIDControlPoint = 0x2A4C,
        HIDInformation = 0x2A4A,
        IEEE11073_20601RegulatoryCertificationDataList = 0x2A2A,
        IntermediateCuffPressure = 0x2A36,
        IntermediateTemperature = 0x2A1E,
        LocalTimeInformation = 0x2A0F,
        ManufacturerNameString = 0x2A29,
        MeasurementInterval = 0x2A21,
        ModelNumberString = 0x2A24,
        NewAlert = 0x2A46,
        PeripheralPreferredConnectionParameters = 0x2A04,
        PeripheralPrivacyFlag = 0x2A02,
        PnPID = 0x2A50,
        ProtocolMode = 0x2A4E,
        ReconnectionAddress = 0x2A03,
        RecordAccessControlPoint = 0x2A52,
        ReferenceTimeInformation = 0x2A14,
        Report = 0x2A4D,
        ReportMap = 0x2A4B,
        RingerControlPoint = 0x2A40,
        RingerSetting = 0x2A41,
        RSCFeature = 0x2A54,
        RSCMeasurement = 0x2A53,
        SCControlPoint = 0x2A55,
        ScanIntervalWindow = 0x2A4F,
        ScanRefresh = 0x2A31,
        SensorLocation = 0x2A5D,
        SerialNumberString = 0x2A25,
        ServiceChanged = 0x2A05,
        SoftwareRevisionString = 0x2A28,
        SupportedNewAlertCategory = 0x2A47,
        SupportedUnreadAlertCategory = 0x2A48,
        SystemID = 0x2A23,
        TemperatureMeasurement = 0x2A1C,
        TemperatureType = 0x2A1D,
        TimeAccuracy = 0x2A12,
        TimeSource = 0x2A13,
        TimeUpdateControlPoint = 0x2A16,
        TimeUpdateState = 0x2A17,
        TimewithDST = 0x2A11,
        TimeZone = 0x2A0E,
        TxPowerLevel = 0x2A07,
        UnreadAlertStatus = 0x2A45,
        AggregateInput = 0x2A5A,
        AnalogInput = 0x2A58,
        AnalogOutput = 0x2A59,
        CyclingPowerControlPoint = 0x2A66,
        CyclingPowerFeature = 0x2A65,
        CyclingPowerMeasurement = 0x2A63,
        CyclingPowerVector = 0x2A64,
        DigitalInput = 0x2A56,
        DigitalOutput = 0x2A57,
        ExactTime100 = 0x2A0B,
        LNControlPoint = 0x2A6B,
        LNFeature = 0x2A6A,
        LocationandSpeed = 0x2A67,
        Navigation = 0x2A68,
        NetworkAvailability = 0x2A3E,
        PositionQuality = 0x2A69,
        ScientificTemperatureinCelsius = 0x2A3C,
        SecondaryTimeZone = 0x2A10,
        String = 0x2A3D,
        TemperatureinCelsius = 0x2A1F,
        TemperatureinFahrenheit = 0x2A20,
        TimeBroadcast = 0x2A15,
        BatteryLevelState = 0x2A1B,
        BatteryPowerState = 0x2A1A,
        PulseOximetryContinuousMeasurement = 0x2A5F,
        PulseOximetryControlPoint = 0x2A62,
        PulseOximetryFeatures = 0x2A61,
        PulseOximetryPulsatileEvent = 0x2A60,
        SimpleKeyState = 0xFFE1
    }

    /// <summary>
    ///     This enum assists in finding a string representation of a BT SIG assigned value for Descriptor UUIDs
    ///     Reference: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
    /// </summary>
    public enum GattNativeDescriptorUuid : ushort
    {
        CharacteristicExtendedProperties = 0x2900,
        CharacteristicUserDescription = 0x2901,
        ClientCharacteristicConfiguration = 0x2902,
        ServerCharacteristicConfiguration = 0x2903,
        CharacteristicPresentationFormat = 0x2904,
        CharacteristicAggregateFormat = 0x2905,
        ValidRange = 0x2906,
        ExternalReportReference = 0x2907,
        ReportReference = 0x2908
    }

}
